package com.mobilesales.android.services;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;

import android.util.Log;

import com.google.gson.Gson;
import com.mobilesales.android.model.exceptions.ServiceUnavailableException;
import com.mobilesales.android.model.helpers.NullParser;

public abstract class ServiceClientBase {
	public static final String TOKEN_KEY = "__token";
	public static final int TIMEOUT = 10000;
	private Gson gson;
	private Configuration configuration;

	public ServiceClientBase(Configuration configuration) {
		this.configuration = configuration;
		gson = new Gson();
	}

	protected Configuration getConfiguration() {
		return configuration;
	}

	protected String getMethodURL() {
		String mName = Thread.currentThread().getStackTrace()[3]
				.getMethodName();
		;
		mName = mName.substring(0, 1).toUpperCase() + mName.substring(1);
		return configuration.getServiceURL() + "/" + mName;
	}

	protected Object get(String requestUrl, Class<?> returnedType)
			throws ServiceUnavailableException {
		Object resultT = null;
		try{
			DefaultHttpClient httpClient = getHttpClient();
	
			HttpGet request = new HttpGet(requestUrl);
			request.setHeader(TOKEN_KEY, configuration.getToken());
			Log.i("get_request", requestUrl);
			HttpResponse response = httpClient.execute(request);
			HttpEntity responseEntity = response.getEntity();
			BufferedReader json = new BufferedReader(new InputStreamReader(
					responseEntity.getContent()));
			String jsonString = readWithStringBuilder(json);
			String jsonNoNulls = NullParser.removeNulls(jsonString);
			Log.i("get_response", jsonString);
			resultT = gson.fromJson(jsonNoNulls, returnedType);
		} catch (Exception e){
			Log.e("exc_get", e.getMessage(), e);
			throw new ServiceUnavailableException(e.getMessage());
		}
		return resultT;
	}
	
	protected List<Object> getList(String requestUrl, Type returnedListType) throws ServiceUnavailableException{
		List<Object> resultObjects = null;
		try{
			DefaultHttpClient httpClient = getHttpClient();
			HttpGet request = new HttpGet(requestUrl);
			request.setHeader(TOKEN_KEY, configuration.getToken());
			Log.i("getList_request", requestUrl);
			HttpResponse response = httpClient.execute(request);
			HttpEntity responseEntity = response.getEntity();
			BufferedReader json = new BufferedReader(new InputStreamReader(
					responseEntity.getContent()));
			String jsonString = readWithStringBuilder(json);
			String jsonNoNulls = NullParser.removeNulls(jsonString);
			Log.i("getList_response", jsonString);
			resultObjects = gson.fromJson(jsonNoNulls, returnedListType);
		} catch (Exception e){
			Log.e("exc_getlist", e.getMessage(), e);
			throw new ServiceUnavailableException(e.getMessage());
		}
		return resultObjects;
	}

	protected <T> void post(String requestUrl, T requestObject) throws ServiceUnavailableException {
		try{
	        HttpResponse response = getPostResponse(requestUrl, requestObject);
	        Log.i("post_response", response.getStatusLine().toString());
	        if(response.getStatusLine().getStatusCode() != 200){
	        	throw new ServiceUnavailableException(response.getStatusLine().toString());
	        }
		} catch (Exception e){
			Log.e("exc_post", e.getMessage(), e);
			throw new ServiceUnavailableException(e.getMessage());
		}
	}
	
	protected <T> Object postReturn(String requestUrl, T requestObject, Class<?> returnedType) throws ServiceUnavailableException {
		Object resultObject = null;
		try{
	        HttpResponse response = getPostResponse(requestUrl, requestObject);
	        Log.i("postReturn_response", response.getStatusLine().toString());
	        if(response.getStatusLine().getStatusCode() != 200){
	        	throw new ServiceUnavailableException(response.getStatusLine().toString());
	        }
			HttpEntity responseEntity = response.getEntity();
			BufferedReader jsonResponse = new BufferedReader(new InputStreamReader(
					responseEntity.getContent()));
			String jsonString = readWithStringBuilder(jsonResponse);
	        Log.i("postReturn_response", jsonString);
			String jsonNoNulls = NullParser.removeNulls(jsonString);
			resultObject = gson.fromJson(jsonNoNulls, returnedType);
		} catch (Exception e){
			Log.e("exc_post", e.getMessage(), e);
			throw new ServiceUnavailableException(e.getMessage());
		}
		return resultObject;
	}
	
	private <T> HttpResponse getPostResponse(String requestUrl, T requestObject) throws ClientProtocolException, IOException{
		DefaultHttpClient httpClient = getHttpClient();
		HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);
		
        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            public boolean retryRequest(IOException exception, int executionCount,
                    HttpContext context) {
                // retry a max of 5 times
                if(executionCount >= 5){
                    return false;
                }
                if(exception instanceof NoHttpResponseException){
                    return true;
                } else if (exception instanceof ClientProtocolException){
                    return true;
                } 
                return false;
            }
        };
        httpClient.setHttpRequestRetryHandler(retryHandler);		
		
		HttpPost request = new HttpPost(requestUrl);
		Log.i("postReturn_request", requestUrl);
		request.setHeader(TOKEN_KEY, configuration.getToken());
		request.setHeader("Accept", "application/json");
        request.setHeader("Content-type", "application/json;charset=UTF-8");
		String json = gson.toJson(requestObject, requestObject.getClass());
		Log.d("serializedObject", json);
        StringEntity entity = new StringEntity(json, HTTP.UTF_8);
        request.setEntity(entity);
        HttpResponse response = httpClient.execute(request);
        return response;
	}
	
	private DefaultHttpClient getHttpClient(){
		DefaultHttpClient httpClient = new DefaultHttpClient();
		HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);
		HttpParams httpParameters = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParameters, TIMEOUT);
		HttpConnectionParams.setSoTimeout(httpParameters, TIMEOUT);
		httpClient.setParams(httpParameters);
		
        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            public boolean retryRequest(IOException exception, int executionCount,
                    HttpContext context) {
                // retry a max of 5 times
                if(executionCount >= 5){
                    return false;
                }
                if(exception instanceof NoHttpResponseException){
                    return true;
                } else if (exception instanceof ClientProtocolException){
                    return true;
                } 
                return false;
            }
        };
        httpClient.setHttpRequestRetryHandler(retryHandler);
		
		
		
		return httpClient;
	}
	
	private String readWithStringBuilder(Reader fr) throws IOException {
		BufferedReader br = new BufferedReader(fr);
		String line;
		StringBuilder result = new StringBuilder();
		while ((line = br.readLine()) != null) {
			result.append(line);
		}
		return result.toString();
	}
}
